Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.97% covered (success)
94.97%
378 / 398
50.00% covered (danger)
50.00%
6 / 12
CRAP
n/a
0 / 0
Pressbooks\L10n\supported_languages
100.00% covered (success)
100.00%
145 / 145
100.00% covered (success)
100.00%
1 / 1
1
Pressbooks\L10n\wplang_codes
100.00% covered (success)
100.00%
143 / 143
100.00% covered (success)
100.00%
1 / 1
1
Pressbooks\L10n\get_locale
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
Pressbooks\L10n\load_plugin_textdomain
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
Pressbooks\L10n\override_core_strings
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
Pressbooks\L10n\include_core_overrides
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
3.33
Pressbooks\L10n\set_locale
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
12.69
Pressbooks\L10n\set_root_locale
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
Pressbooks\L10n\install_book_locale
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
6.09
Pressbooks\L10n\update_user_locale
33.33% covered (danger)
33.33%
4 / 12
0.00% covered (danger)
0.00%
0 / 1
12.41
Pressbooks\L10n\romanize
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
4
Pressbooks\L10n\get_book_language
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * @author  Pressbooks <code@pressbooks.com>
4 * @license GPLv3 (or any later version)
5 */
6
7namespace Pressbooks\L10n;
8
9/**
10 * KindleGen is based on Mobipocket Creator and apparently supports only the following language codes.
11 * This populates the language dropdown on the Book Info page.
12 *
13 * @see http://www.mobileread.com/forums/showpost.php?p=2453537&postcount=2
14 * @return array
15 */
16function supported_languages() {
17
18    $languages = [
19        '' => '&nbsp;',
20        'af' => 'Afrikaans',
21        'sq' => 'Albanian',
22        'ar' => 'Arabic',
23        'ar-dz' => 'Arabic (Algeria)',
24        'ar-bh' => 'Arabic (Bahrain)',
25        'ar-eg' => 'Arabic (Egypt)',
26        'ar-jo' => 'Arabic (Jordan)',
27        'ar-kw' => 'Arabic (Kuwait)',
28        'ar-lb' => 'Arabic (Lebanon)',
29        'ar-ma' => 'Arabic (Morocco)',
30        'ar-om' => 'Arabic (Oman)',
31        'ar-qa' => 'Arabic (Qatar)',
32        'ar-sa' => 'Arabic (Saudi Arabia)',
33        'ar-sy' => 'Arabic (Syria)',
34        'ar-tn' => 'Arabic (Tunisia)',
35        'ar-ae' => 'Arabic (U.A.E.)',
36        'ar-ye' => 'Arabic (Yemen)',
37        'hy' => 'Armenian',
38        'az' => 'Azeri',
39        'eu' => 'Basque',
40        'be' => 'Belarusian',
41        'bn' => 'Bengali',
42        'bg' => 'Bulgarian',
43        'ca' => 'Catalan',
44        'zh' => 'Chinese',
45        'zh-hk' => 'Chinese (Hong Kong)',
46        'zh-cn' => 'Chinese (PRC)',
47        'zh-sg' => 'Chinese (Singapore)',
48        'zh-tw' => 'Chinese (Taiwan)',
49        'hr' => 'Croatian',
50        'cs' => 'Czech',
51        'da' => 'Danish',
52        'nl' => 'Dutch',
53        'nl-be' => 'Dutch (Belgium)',
54        'en' => 'English',
55        'en-au' => 'English (Australia)',
56        'en-bz' => 'English (Belize)',
57        'en-ca' => 'English (Canada)',
58        'en-ie' => 'English (Ireland)',
59        'en-jm' => 'English (Jamaica)',
60        'en-nz' => 'English (New Zealand)',
61        'en-ph' => 'English (Philippines)',
62        'en-za' => 'English (South Africa)',
63        'en-tt' => 'English (Trinidad)',
64        'en-gb' => 'English (United Kingdom)',
65        'en-us' => 'English (United States)',
66        'en-zw' => 'English (Zimbabwe)',
67        'et' => 'Estonian',
68        'fo' => 'Faeroese',
69        'fa' => 'Farsi',
70        'fi' => 'Finnish',
71        'fr-be' => 'French (Belgium)',
72        'fr-ca' => 'French (Canada)',
73        'fr' => 'French',
74        'fr-lu' => 'French (Luxembourg)',
75        'fr-mc' => 'French (Monaco)',
76        'fr-ch' => 'French (Switzerland)',
77        'ka' => 'Georgian',
78        'de' => 'German',
79        'de-at' => 'German (Austria)',
80        'de-li' => 'German (Liechtenstein)',
81        'de-lu' => 'German (Luxembourg)',
82        'de-ch' => 'German (Switzerland)',
83        'el' => 'Greek',
84        'gu' => 'Gujarati',
85        'he' => 'Hebrew',
86        'hi' => 'Hindi',
87        'hu' => 'Hungarian',
88        'is' => 'Icelandic',
89        'id' => 'Indonesian',
90        'it' => 'Italian',
91        'it-ch' => 'Italian (Switzerland)',
92        'ja' => 'Japanese',
93        'kn' => 'Kannada',
94        'kk' => 'Kazakh',
95        'x-kok' => 'Konkani',
96        'ko' => 'Korean',
97        'lv' => 'Latvian',
98        'lt' => 'Lithuanian',
99        'mk' => 'Macedonian',
100        'ms' => 'Malay',
101        'ml' => 'Malayalam',
102        'mt' => 'Maltese',
103        'mr' => 'Marathi',
104        'ne' => 'Nepali',
105        'no' => 'Norwegian',
106        'nb' => 'Norwegian (Bokm&aring;l)',
107        'nn' => 'Norwegian (Nynorsk)',
108        'or' => 'Oriya',
109        'pl' => 'Polish',
110        'pt' => 'Portuguese',
111        'pt-br' => 'Portuguese (Brazil)',
112        'pa' => 'Punjabi',
113        'rm' => 'Rhaeto-Romanic',
114        'ro' => 'Romanian',
115        'ro-mo' => 'Romanian (Moldova)',
116        'ru' => 'Russian',
117        'ru-mo' => 'Russian (Moldova)',
118        'sz' => 'Sami (Lappish)',
119        'sa' => 'Sanskrit',
120        'sr' => 'Serbian',
121        'sk' => 'Slovak',
122        'sl' => 'Slovenian',
123        'sb' => 'Sorbian',
124        'es' => 'Spanish',
125        'es-ar' => 'Spanish (Argentina)',
126        'es-bo' => 'Spanish (Bolivia)',
127        'es-cl' => 'Spanish (Chile)',
128        'es-co' => 'Spanish (Colombia)',
129        'es-cr' => 'Spanish (Costa Rica)',
130        'es-do' => 'Spanish (Dominican Republic)',
131        'es-ec' => 'Spanish (Ecuador)',
132        'es-sv' => 'Spanish (El Salvador)',
133        'es-gt' => 'Spanish (Guatemala)',
134        'es-hn' => 'Spanish (Honduras)',
135        'es-mx' => 'Spanish (Mexico)',
136        'es-ni' => 'Spanish (Nicaragua)',
137        'es-pa' => 'Spanish (Panama)',
138        'es-py' => 'Spanish (Paraguay)',
139        'es-pe' => 'Spanish (Peru)',
140        'es-pr' => 'Spanish (Puerto Rico)',
141        'es-uy' => 'Spanish (Uruguay)',
142        'es-ve' => 'Spanish (Venezuela)',
143        'sx' => 'Sutu',
144        'sw' => 'Swahili',
145        'sv' => 'Swedish',
146        'sv-fi' => 'Swedish (Finland)',
147        'ta' => 'Tamil',
148        'tt' => 'Tatar',
149        'te' => 'Telugu',
150        'th' => 'Thai',
151        'ts' => 'Tsonga',
152        'tn' => 'Tswana',
153        'tr' => 'Turkish',
154        'uk' => 'Ukranian',
155        'ur' => 'Urdu',
156        'uz' => 'Uzbek',
157        'vi' => 'Vietnamese',
158        'xh' => 'Xhosa',
159        'zu' => 'Zulu',
160    ];
161
162    asort( $languages );
163
164    return $languages;
165}
166
167/**
168 * This helps us convert KindleGen language codes to WordPress-compatible ones and vice versa.
169 *
170 * @return array
171 */
172function wplang_codes() {
173
174    $languages = [
175        'af' => 'af', // Afrikaans
176        'sq' => 'sq', // Albanian
177        'ar' => 'ar', // Arabic
178        'ar-dz' => 'ar', // Arabic (Algeria)
179        'ar-bh' => 'ar', // Arabic (Bahrain)
180        'ar-eg' => 'ar', // Arabic (Egypt)
181        'ar-jo' => 'ar', // Arabic (Jordan)
182        'ar-kw' => 'ar', // Arabic (Kuwait)
183        'ar-lb' => 'ar', // Arabic (Lebanon)
184        'ar-ma' => 'ary', // Arabic (Morocco)
185        'ar-om' => 'ar', // Arabic (Oman)
186        'ar-qa' => 'ar', // Arabic (Qatar)
187        'ar-sa' => 'ar', // Arabic (Saudi Aria)
188        'ar-sy' => 'ar', // Arabic (Syria)
189        'ar-tn' => 'ar', // Arabic (Tunisia)
190        'ar-ae' => 'ar', // Arabic (U.A.E.)
191        'ar-ye' => 'ar', // Arabic (Yemen)
192        'hy' => 'hy', // Armenian
193        'az' => 'az', // Azerbaijani
194        'eu' => 'eu', // Basque
195        'be' => '', // Belarusian
196        'bn' => 'bn_BD', // Bengali
197        'bg' => 'bg_BG', // Bulgarian
198        'ca' => 'ca', // Catalan
199        'zh' => 'zh_CN', // Chinese
200        'zh-hk' => 'zh_HK', // Chinese (Hong Kong)
201        'zh-cn' => 'zh_CN', // Chinese (PRC)
202        'zh-sg' => 'zh_CN', // Chinese (Singapore)
203        'zh-tw' => 'zh_TW', // Chinese (Taiwan)
204        'hr' => 'hr', // Croatian
205        'cs' => 'cs_CZ', // Czech
206        'da' => 'da_DK', // Danish
207        'nl' => 'nl_NL', // Dutch
208        'nl-be' => 'nl_NL', // Dutch (Belgium)
209        'en' => 'en_US', // English (United States)
210        'en-au' => 'en_AU', // English (Australia)
211        'en-bz' => 'en_US', // English (Belize)
212        'en-ca' => 'en_CA', // English (Canada)
213        'en-ie' => 'en_UK', // English (Ireland)
214        'en-jm' => 'en_US', // English (Jamaica)
215        'en-nz' => 'en_NZ', // English (Aotearoa New Zealand)
216        'en-ph' => 'en_US', // English (Philippines)
217        'en-za' => 'en_ZA', // English (South Africa)
218        'en-tt' => 'en_US', // English (Trinidad)
219        'en-gb' => 'en_GB', // English (United Kingdom)
220        'en-us' => 'en_US', // English (United States)
221        'en-zw' => 'en_US', // English (Zimbabwe)
222        'et' => 'et', // Estonian
223        'fo' => '', // Faeroese
224        'fa' => 'fa_IR', // Farsi
225        'fi' => 'fi', // Finnish
226        'fr' => 'fr_FR', // French
227        'fr-ca' => 'fr_CA', // French (Canada)
228        'fr-be' => 'fr_FR', // French (Belgium)
229        'fr-lu' => 'fr_FR', // French (Luxembourg)
230        'fr-mc' => 'fr_FR', // French (Monaco)
231        'fr-ch' => 'fr_FR', // French (Switzerland)
232        'ka' => 'ka_GE', // Georgian
233        'de' => 'de_DE', // German
234        'de-at' => 'de_DE', // German (Austria)
235        'de-li' => 'de_DE', // German (Liechtenstein)
236        'de-lu' => 'de_DE', // German (Luxembourg)
237        'de-ch' => 'de_CH', // German (Switzerland)
238        'el' => 'el', // Greek
239        'gu' => 'gu', // Gujarati
240        'he' => 'he_IL', // Hebrew
241        'hi' => 'hi_IN', // Hindi
242        'hu' => 'hu_HU', // Hungarian
243        'is' => 'is_IS', // Icelandic
244        'id' => 'id_ID', // Indonesian
245        'it' => 'it_IT', // Italian
246        'it-ch' => 'it_IT', // Italian (Switzerland)
247        'ja' => 'ja', // Japanese
248        'kn' => '', // Kannada
249        'kk' => '', // Kazakh
250        'x-kok' => '', // Konkani
251        'ko' => 'ko_KR', // Korean
252        'lv' => 'lv', // Latvian
253        'lt' => 'lt_LT', // Lithuanian
254        'mk' => 'mk_MK', // Macedonian
255        'ms' => 'ms_MY', // Malay
256        'ml' => '', // Malayalam
257        'mt' => '', // Maltese
258        'mr' => 'mr', // Marathi
259        'ne' => '', // Nepali
260        'no' => 'nb_NO', // Norwegian (Bokmal)
261        'nb' => 'nb_NO', // Norwegian (Bokmal)
262        'nn' => 'nn_NO', // Norwegian (Nynorsk)
263        'or' => 'Oriya',
264        'pl' => 'pl_PL', // Polish
265        'pt' => 'pt_PT', // Portuguese (Portugal)
266        'pt-br' => 'pt_BR', // Portuguese (Brazil)
267        'pa' => '', // Punjabi
268        'rm' => '', // Rhaeto-Romanic
269        'ro' => 'ro_RO', // Romanian
270        'ro-mo' => 'ro_RO', // Romanian (Moldova)
271        'ru' => 'ru_RU', // Russian
272        'ru-mo' => 'ru_RU', // Russian (Moldova)
273        'sz' => '', // Sami (Lappish)
274        'sa' => '', // Sanskrit
275        'sr' => 'sr_RS', // Serbian
276        'sk' => 'sk_SK', // Slovak
277        'sl' => 'sl_SI', // Slovenian
278        'sb' => '', // Sorbian
279        'es' => 'es_ES', // Spanish
280        'es-ar' => 'es_AR', // Spanish (Argentina)
281        'es-bo' => '', // Spanish (Bolivia)
282        'es-cl' => 'es_CL', // Spanish (Chile)
283        'es-co' => 'es_CO', // Spanish (Colombia)
284        'es-cr' => '', // Spanish (Costa Rica)
285        'es-do' => '', // Spanish (Dominican Republic)
286        'es-ec' => '', // Spanish (Ecuador)
287        'es-sv' => '', // Spanish (El Salvador)
288        'es-gt' => 'es_GT', // Spanish (Guatemala)
289        'es-hn' => '', // Spanish (Honduras)
290        'es-mx' => 'es_MX', // Spanish (Mexico)
291        'es-ni' => '', // Spanish (Nicaragua)
292        'es-pa' => '', // Spanish (Panama)
293        'es-py' => '', // Spanish (Paraguay)
294        'es-pe' => 'es_PE', // Spanish (Peru)
295        'es-pr' => '', // Spanish (Puerto Rico)
296        'es-uy' => '', // Spanish (Uruguay)
297        'es-ve' => 'es_VE', // Spanish (Venezuela)
298        'sx' => '', // Sutu
299        'sw' => '', // Swahili
300        'sv' => 'sv_SE', // Swedish
301        'sv-fi' => 'sv_SE', // Swedish (Finland)
302        'ta' => '', // Tamil
303        'tt' => '', // Tatar
304        'te' => '', // Telugu
305        'th' => 'th', // Thai
306        'ts' => '', // Tsonga
307        'tn' => '', // Tswana
308        'tr' => 'tr_TR', // Turkish
309        'uk' => 'uk', // Ukrainian
310        'ur' => '', // Urdu
311        'uz' => '', // Uzbek
312        'vi' => 'vi', // Vietnamese
313        'xh' => '', // Xhosa
314        'zu' => '', // Zulu
315    ];
316
317    return $languages;
318}
319
320/**
321 * Override get_locale
322 * For performance reasons, we only want functions in this namespace to call WP get_locale once.
323 * (avoid triggering `apply_filters( 'locale', $locale )` ad nausea)
324 *
325 * @return string
326 */
327function get_locale() {
328    // If the user has set a locale, use it
329    if ( function_exists( 'wp_get_current_user' ) && is_admin() ) {
330        $user = wp_get_current_user();
331        if ( $user->locale ) {
332            return $user->locale;
333        }
334    }
335    // Else, use the global locale
336    global $locale;
337    if ( ! empty( $locale ) ) {
338        return $locale;
339    }
340    return \get_locale();
341}
342
343/**
344 * When multiple mo-files are loaded for the same domain, the first found translation will be used. To allow for easier
345 * customization we load from the WordPress languages directory by default then fallback on our own, if any.
346 *
347 * @see \load_plugin_textdomain
348 * @see \Translations::merge_with
349 *
350 * @param string $locale (optional)
351 */
352function load_plugin_textdomain( $locale = '' ) {
353    if ( empty( $locale ) ) {
354        $locale = get_locale();
355    }
356    $domain = 'pressbooks';
357    $locale = apply_filters( 'plugin_locale', $locale, $domain );
358    $mofile = $domain . '-' . $locale . '.mo';
359
360    // Start by unloading all translations
361    unload_textdomain( $domain );
362
363    // Find, merge the translations we want
364    $path = WP_LANG_DIR . '/pressbooks/' . $mofile;
365    load_textdomain( $domain, $path );
366
367    $path = WP_LANG_DIR . '/plugins/' . $mofile;
368    load_textdomain( $domain, $path );
369
370    $path = WP_PLUGIN_DIR . '/pressbooks/languages/' . $mofile;
371    if ( ! load_textdomain( $domain, $path ) ) {
372        $path = __DIR__ . '/../../languages/' . $mofile;
373        load_textdomain( $domain, $path );
374    }
375}
376
377/**
378 * Change core WordPress strings.
379 *
380 * @param $translated
381 * @param $original
382 * @param $domain
383 *
384 * @return mixed
385 */
386function override_core_strings( $translated, $original, $domain ) {
387    if ( $original === 'put your unique phrase here' ) {
388        return $original;
389    }
390
391    $overrides = include_core_overrides();
392
393    if ( isset( $overrides[ $original ] ) ) {
394        $translations = get_translations_for_domain( $domain );
395        $translated = $translations->translate( $overrides[ $original ] ); // @codingStandardsIgnoreLine
396    }
397
398    return $translated;
399}
400
401/**
402 * Include the core WordPress override file.
403 * Looks for ./languages/core-en_US.php, where "en_US" is defined by get_locale()
404 * Expects $overrides array.
405 * For performance reasons this function will include the file only once.
406 *
407 * @return array
408 */
409function include_core_overrides() {
410
411    // Cheap cache
412    static $_overrides = [];
413
414    $locale = apply_filters( 'plugin_locale', get_locale(), 'pressbooks' );
415    $filename = 'core-' . strtolower( str_replace( '_', '-', $locale ) ) . '.php';
416    $filepath = PB_PLUGIN_DIR . 'languages/' . $filename;
417
418    if ( ! isset( $_overrides[ $locale ] ) ) {
419        $_overrides[ $locale ] = [];
420        if ( file_exists( $filepath ) ) {
421            $_overrides[ $locale ] = include( $filepath );
422        }
423    }
424
425    return $_overrides[ $locale ];
426}
427
428/**
429 * Hook for add_filter('locale ', ...), change the user interface language
430 *
431 * @param string $lang
432 *
433 * @return string
434 */
435function set_locale( $lang ) {
436
437    // Cheap cache
438    static $loc = '__UNSET__';
439
440    if ( '__UNSET__' === $loc ) {
441        $book_lang = get_book_language();
442        if ( is_admin() ) {
443            // If user locale isn't set, use the book information value.
444            if ( function_exists( 'wp_get_current_user' ) && ! get_user_option( 'locale' ) ) {
445                $locations = \Pressbooks\L10n\wplang_codes();
446                $loc = $locations[ $book_lang ];
447            }
448        } elseif ( isset( $GLOBALS['pagenow'] ) && 'wp-signup.php' === $GLOBALS['pagenow'] ) {
449            // If we're on the registration page, use the global setting.
450            $loc = get_site_option( 'WPLANG' );
451        } else {
452            // Use the book information value.
453            $locations = \Pressbooks\L10n\wplang_codes();
454            $loc = $locations[ $book_lang ];
455        }
456    }
457
458    // Return the language
459    if ( '__UNSET__' === $loc ) {
460        return $lang;
461    } else {
462        return ( $loc ? $loc : $lang );
463    }
464}
465
466/**
467 * Hook for add_filter('locale ', ...), change the user interface language
468 *
469 * @param string $lang
470 *
471 * @return string
472 */
473function set_root_locale( $lang ) {
474    // Try to retrieve the network setting
475    $loc = get_site_option( 'WPLANG' );
476    return ( $loc ? $loc : $lang );
477}
478
479/**
480 * When a user changes their book's language, try to install the corresponding language pack.
481 *
482 * @since 3.9.6
483 *
484 * @param int $meta_id The metadata ID
485 * @param int $post_id The book information post ID
486 * @param string $meta_key The metadata key
487 * @param string $meta_value The metadata value
488 *
489 * @return string|bool Returns the language code if successfully downloaded
490 *                     (or already installed), or false on failure.
491 */
492function install_book_locale( $meta_id, $post_id, $meta_key, $meta_value ) {
493    if ( 'pb_language' !== $meta_key ) {
494        return false;
495    }
496
497    $languages = wplang_codes();
498    $locale = $languages[ $meta_value ];
499    if ( '' !== $locale && 'en_US' !== $locale ) {
500        require_once( ABSPATH . '/wp-admin/includes/translation-install.php' );
501        $result = \wp_download_language_pack( $locale );
502        if ( $result ) {
503            if ( ! empty( $GLOBALS['wp_locale_switcher'] ) ) {
504                // We have a new language, reset locale switcher so that it knows the new language is available
505                // @see wp-settings.php
506                $GLOBALS['wp_locale_switcher'] = new \WP_Locale_Switcher();
507                $GLOBALS['wp_locale_switcher']->init();
508            }
509            return $result;
510        } else {
511            $supported_languages = supported_languages();
512            $_SESSION['pb_errors'][] = sprintf( __( 'Please contact your system administrator if you would like them to install extended %s language support for the Pressbooks interface.', 'pressbooks' ), $supported_languages[ $meta_value ] );
513        }
514    }
515
516    return false;
517}
518
519/**
520 * Update previous user interface language meta value to WP 4.7 user locale, try to install the corresponding language pack.
521 *
522 * @since 3.9.6
523 */
524function update_user_locale() {
525    if ( function_exists( 'get_user_meta' ) ) {
526        $locale = get_user_meta( get_current_user_id(), 'user_interface_lang', true );
527        if ( $locale && 'en_US' !== $locale ) {
528            update_user_meta( get_current_user_id(), 'locale', $locale );
529            require_once( ABSPATH . '/wp-admin/includes/translation-install.php' );
530            $result = \wp_download_language_pack( $locale );
531            if ( false === $result ) {
532                $wplang_codes = wplang_codes();
533                $supported_languages = supported_languages();
534                $lang = array_search( $locale, $wplang_codes, true );
535                $_SESSION['pb_errors'][] = sprintf( __( 'Please contact your system administrator if you would like them to install extended %s language support for the Pressbooks interface.', 'pressbooks' ), $supported_languages[ $lang ] );
536            }
537        }
538        delete_user_meta( get_current_user_id(), 'user_interface_lang' );
539    }
540}
541
542/**
543 * Convert integer to roman numeral
544 *
545 * @param int $integer
546 *
547 * @return string
548 */
549function romanize( $integer ) {
550
551    $integer = absint( $integer );
552
553    $table = [
554        'M' => 1000,
555        'CM' => 900,
556        'D' => 500,
557        'CD' => 400,
558        'C' => 100,
559        'XC' => 90,
560        'L' => 50,
561        'XL' => 40,
562        'X' => 10,
563        'IX' => 9,
564        'V' => 5,
565        'IV' => 4,
566        'I' => 1,
567    ];
568    $return = '';
569    while ( $integer > 0 ) {
570        foreach ( $table as $rom => $arb ) {
571            if ( $integer >= $arb ) {
572                $integer -= $arb;
573                $return .= $rom;
574                break;
575            }
576        }
577    }
578
579    return $return;
580}
581
582/**
583 * Get book language
584 *
585 * We used to get `pb_language` by calling \Pressbooks\Book::getBookInformation()
586 * When we introduced the Pressbooks Five data model, and if called before init, then that function would go into infinite recursion.
587 *
588 * @since 5.0.0
589 *
590 * @return string
591 */
592function get_book_language() {
593    // Book Language
594    $meta_post_id = ( new \Pressbooks\Metadata() )->getMetaPostId();
595    if ( $meta_post_id ) {
596        $book_lang = get_post_meta( $meta_post_id, 'pb_language', true );
597    }
598    if ( empty( $book_lang ) ) {
599        $book_lang = 'en';
600    }
601    return $book_lang;
602}
603